#include "logger.hpp"

#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#include <io.h>
#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif

#include <sys/timeb.h>
#include <cstdarg>
#include <cstring>
#include <sstream>
#include <iomanip>

#ifdef _WIN32
#define COLOR_NONE        FOREGROUND_INTENSITY
#define COLR_RED          FOREGROUND_RED
#define COLOR_L_RED       FOREGROUND_INTENSITY | FOREGROUND_RED
#define COLOR_GREEN       FOREGROUND_GREEN
#define COLOR_L_GREEN     FOREGROUND_INTENSITY | FOREGROUND_GREEN
#define COLOR_YELLOW      FOREGROUND_RED | FOREGROUND_GREEN
#define COLOR_L_YELLOW    FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN
#define COLOR_BLUE        FOREGROUND_BLUE
#define COLOR_L_BLUE      FOREGROUND_INTENSITY | FOREGROUND_BLUE
#define COLOR_PURPLE      FOREGROUND_RED | FOREGROUND_BLUE
#define COLOR_L_PURPLE    FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE
#define COLOR_CYAN        FOREGROUND_GREEN | FOREGROUND_BLUE
#define COLOR_L_CYAN      FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE
#define COLOR_WHITE       FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
#define COLOR_L_WHITE     FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
#else
#define COLOR_NONE        "\033[0m"
#define COLOR_RED         "\033[0;31m"
#define COLOR_L_RED       "\033[1;31m"
#define COLOR_GREEN       "\033[0;32m"
#define COLOR_L_GREEN     "\033[1;32m"
#define COLOR_YELLOW      "\033[0;33m"
#define COLOR_L_YELLOW    "\033[1;33m"
#define COLOR_BLUE        "\033[0;34m"
#define COLOR_L_BLUE      "\033[1;34m"
#define COLOR_PURPLE      "\033[0;35m"
#define COLOR_L_PURPLE    "\033[1;35m"
#define COLOR_CYAN        "\033[0;36m"
#define COLOR_L_CYAN      "\033[1;36m"
#define COLOR_WHITE       "\033[0;37m"
#define COLOR_L_WHITE     "\033[1;37m"
#endif

#define COLOR_END         COLOR_WHITE
#define COLOR_DEBUG       COLOR_L_BLUE
#define COLOR_INFO        COLOR_L_GREEN
#define COLOR_WARNING     COLOR_L_YELLOW
#define COLOR_ERROR       COLOR_L_RED
#define COLOR_FATAL       COLOR_L_PURPLE

#ifdef _WIN32
#define SET_COLOR(clr)        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), clr)
#define ACCESS(file, mode)    _access(file, mode)
#define MKDIR(path)           _mkdir(path)
#else
#define SET_COLOR(clr)        fprintf(stdout, (clr))
#define ACCESS(file, mode)    access(file, mode)
#define MKDIR(path)           (umask(0),mkdir(path, 0775))
#endif

Logger::Logger()
{
	path_ = "./Log/";
	name_ = "log";
	level_ = kInfo;
	size_ = 4194304;
	output_ = true;
}

bool Logger::Init(const std::string &path, const std::string &name, Level level, size_t size, bool output)
{
	if (path.empty() || name.empty())
	{
		return false;
	}

	if (std::string::npos != name.find_last_of("/\\"))
	{
		return false;
	}

	path_ = path;
	name_ = name;
	level_ = level;
	size_ = size;
	output_ = output;

	if ('/' != path_[path_.size() -1] && '\\' != path_[path_.size() -1])
	{
		path_.push_back('/');
	}

	return true;
}

void Logger::Print(Level level, const std::string &file, unsigned int line, const std::string &function, const char *format, ...)
{
	static const char levels[5][8] = { "DEBUG", "INFO", "WARNING", "ERROR", "FATAL" };

	if (level < level_ || level > kFatal)
	{
		return;
	}

	std::lock_guard<std::mutex> lock(lock_);

	std::string file_name = file;
	size_t name_pos = file_name.find_last_of("/\\");
	if (std::string::npos != name_pos)
	{
		file_name = file_name.substr(name_pos + 1);
	}

	struct timeval tv;
    gettimeofday(&tv, nullptr);
    struct tm time_tm;
    localtime_r(&tv.tv_sec, &time_tm);

    std::stringstream log_date;
    log_date << std::dec << time_tm.tm_year + 1900 << '-';
    log_date << std::dec << time_tm.tm_mon + 1 << '-';
    log_date << std::dec << time_tm.tm_mday;

    std::stringstream log_time;
    log_time << std::dec << std::setw(2) << std::setfill('0') << time_tm.tm_hour << ':';
    log_time << std::dec << std::setw(2) << std::setfill('0') << time_tm.tm_min << ':';
    log_time << std::dec << std::setw(2) << std::setfill('0') << time_tm.tm_sec << '.';
    log_time << std::dec << std::setw(3) << std::setfill('0') << tv.tv_usec / 1000;

	std::string path = path_ + log_date.str() + '/';
	if (0 != ACCESS(path.c_str(), 0))
	{
		MKDIR(path.c_str());
	}

	std::string new_file = path + name_ + '_' + log_date.str() + ".txt";
	FILE *log_file = fopen(new_file.c_str(), "a+");
	if (NULL != log_file)
	{
		fseek(log_file, 0L, SEEK_END);
		if ((size_t)ftell(log_file) > size_)
		{
			fclose(log_file);
			log_file = NULL;
			std::string bak_file = path + name_ + '_' + log_date.str() + ".bak";
			remove(bak_file.c_str());
			if (rename(new_file.c_str(), bak_file.c_str()) == 0)
			{
				log_file = fopen(new_file.c_str(), "w");
			}
		}
	}

	va_list args1;
	va_start(args1, format);

	if (NULL != log_file)
	{
		va_list args2;
		va_start(args2, format);

		fprintf(log_file, "[%s %s %s:%d %s] ", log_time.str().c_str(), levels[level], file_name.c_str(), line, function.c_str());
		memcpy(args2, args1, sizeof(va_list));
		vfprintf(log_file, format, args2);
		va_end(args2);

		fprintf(log_file, "\n");
		fclose(log_file);
	}

	if (output_)
	{
		switch (level)
		{
		case kDebug: SET_COLOR(COLOR_DEBUG); break;
		case kInfo: SET_COLOR(COLOR_INFO); break;
		case kWarning: SET_COLOR(COLOR_WARNING); break;
		case kError: SET_COLOR(COLOR_ERROR); break;
		case kFatal: SET_COLOR(COLOR_FATAL); break;
		default: break;
		}

		fprintf(stdout, "[%s %s %s:%d %s] ", log_time.str().c_str(), levels[level], file_name.c_str(), line, function.c_str());
		vfprintf(stdout, format, args1);

		SET_COLOR(COLOR_END);
		fprintf(stdout, "\n");
	}

	va_end(args1);
}

void Logger::Show(Level level, const std::string &file, unsigned int line, const std::string &function, const std::string &name, const char *memory, size_t size)
{
	std::stringstream buffer;

	buffer << "name:";
	buffer << name;
	buffer << "    size:";
	buffer << std::dec << size;

	for (size_t i = 0; i < size; i++)
	{
		if (0 == i % 16)
		{
			buffer << "\n";
		}
		buffer << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(static_cast<unsigned char>(memory[i])) << ' ';
	}

	Print(level, file, line, function, "\n%s", buffer.str().c_str());
}